﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Principal;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using TestMods.Building_Blocks;

namespace TestMods
{
    class AddStableTags
    {
        private const string ProjectPath = "C:\\IIS\\wwwroot\\mb-alpha-qa";
        private static readonly string UserName = WindowsIdentity.GetCurrent().Name.Replace("SLO\\", "");
        private static int _numFiles = 0;

        static void Main(string[] args)
        {
        	int numFilePosn = 0;

            if(args.Length != 3 && args.Length != 2)
            {
                Console.WriteLine("Invalid number of args. Need [whether to run in user mode], number of files to check, and directory to find files in.");
                Environment.Exit(2);
            }

        	bool inUserMode = args.Length == 3 && args[0].Equals("-u");

			if(inUserMode)
			{
				numFilePosn = 1;
			}

            try
            {
                _numFiles = int.Parse(args[numFilePosn]);
                if(_numFiles == 0) { return; }
            } catch(FormatException)
            {
                Console.WriteLine("Invalid syntax. The first argument should be the number of files to check.");
                Environment.Exit(2);
            }

			if(inUserMode)
			{
				int toLookAt = Math.Max(1, _numFiles-5);
				for(int i = toLookAt; i <= _numFiles+5; i++)
				{
					var currTest = SortTests(LoadFiles(args[2], i), i);
					Console.WriteLine(i + " runs gives " + currTest.PassingTests.Count + "passes.");
				}
				Console.WriteLine("\nWhich number of tests would you like to use?");
				_numFiles = int.Parse(Console.ReadLine());
			}

            var results = LoadFiles(args[numFilePosn+1], _numFiles);
            var tests = SortTests(results, _numFiles);

            Console.WriteLine("Adding Stable Tags");
            MarkTests(tests.PassingTests);

            Console.WriteLine("Adding Unstable Tags");
            MarkTests(tests.FailingTests);

            CommitAllChanges();
        }

        private static List<Run> LoadFiles(string dir, int numURLs)
        {
            var buildsText = new WebClient().DownloadString(dir);
            var builds = JsonConvert.DeserializeObject<Builds>(buildsText);

            var runs = new List<Run>();
            for(int i = 0; i < numURLs; i++)
            {
                var url = builds.builds[i].url + "testReport/api/json?pretty=true";
                try
                {
                    var currRunText = new WebClient().DownloadString(url);
                    //Console.WriteLine("Accessing " + url);
                    var currRun = JsonConvert.DeserializeObject<Run>(currRunText);
                    foreach (Case c in currRun.getAllCases())
                    {
                        c.name = c.name.Split('(')[0];
                    }
                    runs.Add(currRun);
                } catch(WebException)
                {
                    numURLs++;
                    if(numURLs > builds.builds.Count)
                    {
                        Console.WriteLine("Not enough runs available in JSON provided.");
                        Environment.Exit(4);
                    }
                }
            }
            return runs;
        }

        private static FullRun SortTests(List<Run> results, int numToLookAt)
        {
            var passingTests = GetPassingTestsForFile(results[0].getAllCases());
            var failingTests = GetFailingTestsForFile(results[0].getAllCases());

            for (int i = 1; i < numToLookAt; i++)
            {
                var currentFilePassing = GetPassingTestsForFile(results[i].getAllCases());
                passingTests = passingTests.Intersect(currentFilePassing).ToList();

                var currentFileFailing = GetFailingTestsForFile(results[i].getAllCases());
                failingTests = failingTests.Union(currentFileFailing).ToList();
            }

            return new FullRun(passingTests, failingTests);
        } 

        private static List<Case> GetFailingTestsForFile(IEnumerable<Case> results)
        {
            return results.Where(currCase => !currCase.status.Equals("PASSED")).ToList();
        } 

        private static List<Case> GetPassingTestsForFile(IEnumerable<Case> results)
        {
            return results.Where(currCase => currCase.status.Equals("PASSED")).ToList();
        }

        private static void MarkTests(IEnumerable<Case> tests)
        {
            foreach(var test in tests)
            {
                var testFile = ProjectPath + test.className.Substring(2).Replace(".", "\\").Replace("Web\\Tests", "Web.Tests") + ".cs";
                ExecuteCommandSync("tf checkout " + testFile);
                var file = File.ReadAllText(testFile);

                switch(test.status)
                {
                    case "PASSED": File.WriteAllText(testFile, MarkPassingAsStable(file, test));
                        break;
                    case "FAILED": File.WriteAllText(testFile, MarkFailingAsUnstable(file, test));
                        break;
                }
            }
        }

        private static string MarkPassingAsStable(string file, Case passingTest)
        {
            Console.WriteLine("Passing Test Name: " + passingTest.name);

            var match = Regex.Match(file, passingTest.RegexWithStableCategory);
            if (!match.Success)
            {
                bool hasUnstableTag = true;

                match = Regex.Match(file, passingTest.RegexWithUnstableCategory);
                if(!match.Success)
                {
                    hasUnstableTag = false;
                    match = Regex.Match(file, passingTest.RegexWithoutCategory);
                    if (!match.Success)
                    {
                        Console.WriteLine("No such test in this file.");
                        return file;
                    }
                }

                string[] splitDelims = { match.Value };
                string[] splitFile = file.Split(splitDelims, StringSplitOptions.RemoveEmptyEntries);
                string fixedSection = hasUnstableTag 
                                            ? ChangeUnstableTagToStable(match.Value) 
                                            : InsertStableTag(match.Value);

                return splitFile[0] + fixedSection + splitFile[1];
            }
            return file;
        }

        private static string MarkFailingAsUnstable(string file, Case failingTest)
        {
            Console.WriteLine("Failing Test Name: " + failingTest.name);

            var match = Regex.Match(file, failingTest.RegexWithStableCategory);
            if (!match.Success)
            {
                match = Regex.Match(file, failingTest.RegexWithUnstableCategory);
                if (!match.Success)
                {
                    match = Regex.Match(file, failingTest.RegexWithoutCategory);
                    if (!match.Success)
                    {
                        Console.WriteLine("No such test in this file.");
                        return file;
                    }

                    string[] splitDelims = { match.Value };
                    string[] splitFile = file.Split(splitDelims, StringSplitOptions.RemoveEmptyEntries);
                    string fixedSection = InsertUnstableTag(match.Value);

                    return splitFile[0] + fixedSection + splitFile[1];
                }
            }
            return file;
        }

        private static string ChangeUnstableTagToStable(string withUnstableTag)
        {
            const string unstableTag = "(\"Unstable\")";
            var lines = Regex.Split(withUnstableTag, "\r\n");
            string fixedString = "";

            foreach (string line in lines)
            {
                if (line.Contains(unstableTag))
                {
                    fixedString += "\r\n" + line.Replace(unstableTag, "(\"Stable\")");
                }
                else
                {
                    fixedString += "\r\n" + line;
                }
            }

            return fixedString.Substring(2);
        }

        private static string InsertStableTag(string withoutStableTag)
        {
            var lines = Regex.Split(withoutStableTag, "\r\n");
            string fixedString = "";

            for(int i = 0; i < lines.Length; i++)
            {
                if(i == lines.Length - 1)
                {
                    fixedString += "\r\n        [Category(\"Stable\")]\r\n" + lines[i];
                } else
                {
                    fixedString += "\r\n" + lines[i];
                }
            }

            return fixedString.Substring(2);
        }

        private static string InsertUnstableTag(string withoutUnstableTag)
        {
            var lines = Regex.Split(withoutUnstableTag, "\r\n");
            string fixedString = "";

            for (int i = 0; i < lines.Length; i++)
            {
                if (i == lines.Length - 1)
                {
                    fixedString += "\r\n        [Category(\"Unstable\")]\r\n" + lines[i];
                }
                else
                {
                    fixedString += "\r\n" + lines[i];
                }
            }

            return fixedString.Substring(2);
        }

        private static void CommitAllChanges()
        {
            var commitSuccess = ExecuteCommandSync("tf checkin /author:" + UserName +
                        " /comment:\"Automated--Adding stable and unstable tags to passing tests.\" /noprompt /override:\"Tests: Automated Check-In\"");

            if (commitSuccess != 0 && commitSuccess != 1)
            {
                Console.WriteLine("Commit failed.");
                Environment.Exit(3);
            }
        }

        private static int ExecuteCommandSync(string command)
        {
            try
            {
                // create the ProcessStartInfo using "cmd" as the program to be run,
                // and "/c " as the parameters.
                // Incidentally, /c tells cmd that we want it to execute the command that follows,
                // and then exit.
                System.Diagnostics.ProcessStartInfo procStartInfo =
                    new System.Diagnostics.ProcessStartInfo("cmd", "/c " + command);

                // The following commands are needed to redirect the standard output.
                // This means that it will be redirected to the Process.StandardOutput StreamReader.
                procStartInfo.RedirectStandardOutput = true;
                procStartInfo.UseShellExecute = false;
                // Do not create the black window.
                procStartInfo.CreateNoWindow = true;
                // Now we create a process, assign its ProcessStartInfo and start it
                System.Diagnostics.Process proc = new System.Diagnostics.Process();
                proc.StartInfo = procStartInfo;
                proc.Start();
                // Get the output into a string
                string result = proc.StandardOutput.ReadToEnd();
                var exitCode = proc.ExitCode;
                // Display the command output.
                return exitCode;
            }
            catch (Exception objException)
            {
                Console.WriteLine(objException.StackTrace);
                return -1;
            }
        }
    }

    sealed class FullRun
    {
        public FullRun(List<Case> passing, List<Case> failing)
        {
            PassingTests = passing;
            FailingTests = failing;
        }

        public List<Case> PassingTests { get; private set; }

        public List<Case> FailingTests { get; private set; }
    }
}
